﻿//-----------------------------------------------------------------------
// <copyright file="CodeCoverage.cs" company="Microsoft Corporation">
//     Copyright (c) Microsoft Corporation 2011. All rights reserved.
// </copyright>
//-----------------------------------------------------------------------

namespace Internal.TeamFoundationServer.Submit
{
    using System;
    using System.Data;
    using System.IO;
    using System.Windows.Forms;
    using Microsoft.VisualStudio.Coverage.Analysis;
    using Properties;

    /// <summary>
    /// CodeCoverage Helper class.
    /// </summary>
    internal class CodeCoverage
    {
        /// <summary>
        /// Prevents a default instance of the <see cref="CodeCoverage"/> class from being created.
        /// </summary>
        private CodeCoverage()
        {
        }

        /// <summary>
        /// Get current code coverage for a specific file.
        /// </summary>
        /// <param name="directory">File directory.</param>
        /// <param name="fileName">File name.</param>
        /// <param name="itemName">Item name.</param>
        /// <returns>Returns Code coverage.</returns>
        internal static decimal GetCurrentCodeCoverage(string directory, string fileName, string itemName)
        {
            // If we are not working with Code Coverage, return -1
            if (!TfsSubmitConfig.ValidateCodeCoverageEnabled)
            {
                return -1;
            }

            // Only interested in C# or VB classes.
            if (!itemName.EndsWith(".cs", StringComparison.OrdinalIgnoreCase) && !itemName.EndsWith(".vb", StringComparison.OrdinalIgnoreCase))
            {
                return -1;
            }

            // Find Solution Directory folder.
            while (!string.IsNullOrEmpty(directory))
            {
                // We are not interested in changes on Test files.
                if (directory.EndsWith("Test", StringComparison.OrdinalIgnoreCase))
                {
                    return -1;
                }

                try
                {
                    string[] files = Directory.GetFiles(directory, "*.sln", SearchOption.TopDirectoryOnly);
                    if (files.Length == 0)
                    {
                        DirectoryInfo di = Directory.GetParent(directory);
                        if (di == null)
                        {
                            // Did not find solution file.
                            return 0;
                        }

                        directory = di.ToString();
                        continue;
                    }
                }
                catch (DirectoryNotFoundException)
                {
                    // Directory not exist.
                    return -1;
                }

                break;
            }

            if (string.IsNullOrEmpty(directory))
            {
                // Solution was not found, no code coverage available.
                return 0;
            }

            // Retrieve the test folder and the coverage file.
            string testResultsFolder;
            string testCoverageFile = GetTestCoverageFile(directory, out testResultsFolder);
            if (testCoverageFile == null)
            {
                // Coverage file was not found, no code coverage available.
                return 0;
            }

            // Check if test file is older than change file.
            if (File.Exists(fileName) && File.GetLastWriteTimeUtc(fileName) > File.GetLastWriteTimeUtc(testCoverageFile))
            {
                // Pending change is older, user should re-run tests before check-in.
                return 0;
            }

            return GetCodeCoverage(testCoverageFile, testResultsFolder, itemName);
        }

        /// <summary>
        /// Finds the TestResult folder.
        /// </summary>
        /// <param name="solutionFolder">Solution folder.</param>
        /// <param name="testResultsFolder">Out Parameter, returns the test result folder.</param>
        /// <returns>Directory path.</returns>
        private static string GetTestCoverageFile(string solutionFolder, out string testResultsFolder)
        {
            testResultsFolder = Path.Combine(solutionFolder, "TestResults");
            if (Directory.Exists(testResultsFolder))
            {
                var info = new DirectoryInfo(testResultsFolder);
                DirectoryInfo[] dirs = info.GetDirectories();
               
                if (dirs.Length > 1)
                {
                    // Sort by last write time
                    Array.Sort(dirs, (x, y) => y.LastWriteTime.CompareTo(x.LastWriteTime));
                }

                testResultsFolder = dirs[0].FullName;
                string[] testFile = Directory.GetFiles(dirs[0].FullName, "data.coverage", SearchOption.AllDirectories);

                if (testFile.Length > 0)
                {
                    // Found Test coverage file.
                    return testFile[0];
                }
            }

            // Did not find the test coverage file.
            return null;
        }

        /// <summary>
        /// Get code coverage.
        /// </summary>
        /// <param name="testCoverageFile">Code coverage file.</param>
        /// <param name="testResultsFolder">Folder where results are located.</param>
        /// <param name="className">Class name.</param>
        /// <returns>Returns code coverage.</returns>
        private static decimal GetCodeCoverage(string testCoverageFile, string testResultsFolder, string className)
        {
            try
            {
                className = className.Replace(".cs", string.Empty);

                var binaryPath = new[] { testResultsFolder + "\\Out" };
                using (CoverageInfo coverageInfo = CoverageInfo.CreateFromFile(testCoverageFile, binaryPath, binaryPath))
                {
                    CoverageDS coverageDataSet = coverageInfo.BuildDataSet();
                    DataRow[] dataRow = coverageDataSet.Tables["Class"].Select("ClassName = '" + className + "'");

                    var blocksCovered = (uint)dataRow[0]["BlocksCovered"];
                    var blocksNotCovered = (uint)dataRow[0]["BlocksNotCovered"];
                    var total = blocksCovered + blocksNotCovered;
                    return Math.Round(Convert.ToDecimal(100 * (decimal)blocksCovered / total), 2);
                }
            }
            catch (CoverageAnalysisException err)
            {
                MessageBox.Show(
                    err.ToString(),
                    Resources.ErrorString,
                    MessageBoxButtons.OK,
                    MessageBoxIcon.Error,
                    MessageBoxDefaultButton.Button1,
                    MessageBoxOptions.RightAlign & MessageBoxOptions.RtlReading);
            }

            return -1;
        }
    }
}
